package com.redis.service;

import com.alibaba.fastjson.JSON;
import com.redis.common.RedisKeyPrefixConst;
import com.redis.common.RedisUtil;
import com.redis.dao.ProductDao;
import com.redis.entity.Product;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @author liyanhao
 * @Date 2023-02-14 11:48
 */
@Service
public class ProductService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ProductDao productDao;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private Redisson redisson;
    public static final Integer PRODUCT_CACHE_TIMEOUT = 60 * 60 * 24;
    public static final String EMPTY_CACHE = "{}";
    public static final String Lock_PRODUCT_HOT_CACHE_PREFIX = "lock:product:hot_cache:";
    public static final String LOCK_PRODUCT_UPDATE_PREFIX = "lock:product:update";
    public static Map<String, Product> productMap = new ConcurrentHashMap<>();


    @Transactional
    public Product create(Product product) {
        Product productResult = productDao.create(product);
        redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), JSON.toJSONString(productResult),
                genProductCacheTimeOut(), TimeUnit.SECONDS);
        return productResult;

    }

    public Product update(Product product) {
        Product productRedult = null;
        //Rlock updateProductLock = redisson.getLock(LOCK_PRODUCT_UPDATE_PREFIX+product.getId())
        RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + product.getId());
        RLock writeLock = readWriteLock.writeLock();
        try {
            productRedult = productDao.update(product);
            redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productRedult.getId(), JSON.toJSONString(productRedult),
                    genProductCacheTimeOut(), TimeUnit.SECONDS);
            productMap.put(RedisKeyPrefixConst.PRODUCT_CACHE + productRedult.getId(), product);
        } finally {
            writeLock.unlock();
        }
        return productRedult;
    }

    public Product get(Long productId) {
        Product product = null;
        String productCacheKey = RedisKeyPrefixConst.PRODUCT_CACHE + productId;
        product = getProductFromCache(productCacheKey);
        if (product != null) {
            return product;
        }
        //DCL
        RLock hotCacheLock = redisson.getLock(Lock_PRODUCT_HOT_CACHE_PREFIX + productId);
        hotCacheLock.lock();
        //boolean result = hotCacheLock.tryLock(3,TimeUnit.SECONDS)
        try {
            Product productFromCache = getProductFromCache(productCacheKey);
            if (productFromCache != null) {
                return product;
            }
            //Rlock updateProduct =redisson.getLock(Lock_PRODUCT_Update_PREFIX+productId)
            RReadWriteLock readWriteLock = redisson.getReadWriteLock(LOCK_PRODUCT_UPDATE_PREFIX + productId);
            RLock readLock = readWriteLock.readLock();
            readLock.lock();
            try {
                product = productDao.get(productId);
                if (product != null) {
                    redisUtil.set(productCacheKey, JSON.toJSONString(product), genProductCacheTimeOut(), TimeUnit.SECONDS);
                    productMap.put(productCacheKey, product);
                } else {
                    redisUtil.set(productCacheKey, EMPTY_CACHE, genProductCacheTimeOut(), TimeUnit.SECONDS);
                }
            } finally {
                readLock.unlock();
            }
        } finally {
            hotCacheLock.unlock();
        }
        return product;

    }

    private Product getProductFromCache(String productCacheKey) {
        Product product = productMap.get(productCacheKey);
        if (product != null) {
            return product;
        }
        Object o = redisTemplate.opsForValue().get(productCacheKey);

        String productStr = redisUtil.get(productCacheKey);
        if (!StringUtils.isEmpty(productStr)) {
            if (EMPTY_CACHE.equals(productStr)) {
                redisUtil.expire(productCacheKey, genEmptyCacheTimeout(), TimeUnit.SECONDS);
                return new Product();
            }
            product = JSON.parseObject(productStr, Product.class);
            //读延期 续期
            redisUtil.expire(productCacheKey, genProductCacheTimeOut(), TimeUnit.SECONDS);
        }
        product = JSON.parseObject(productStr, Product.class);
        redisUtil.expire(productCacheKey, genProductCacheTimeOut(), TimeUnit.SECONDS);
        return product;
    }


    private long genProductCacheTimeOut() {
        return PRODUCT_CACHE_TIMEOUT + new Random().nextInt(5) * 60 * 60;
    }
    private Integer genEmptyCacheTimeout() {
        return 60 + new Random().nextInt(30);
    }



}





















